//
// Created by ultcrt on 2021/5/21.
//

#include <fstream>
#include <chrono>
#include "data_processor.h"
#include <iostream>

using namespace std;
using namespace nlohmann;

uint64_t time_ns() {
    return chrono::duration_cast<chrono::nanoseconds>(chrono::system_clock::now().time_since_epoch()).count();
}

json DataProcessor::process(const nlohmann::json& redis_jsons) {
    json decision = redis_jsons["decision"];
    json detect_inside = redis_jsons["detect_inside"];
    json vehicle_person_front = redis_jsons["vehicle_person_front"];
    json vehicle_person_back = redis_jsons["vehicle_person_back"];
    json road = redis_jsons["road"];

    // Result is not expected
    if (decision.empty() or detect_inside.empty() or vehicle_person_front.empty()
    or vehicle_person_back.empty() or road.empty()) {
        return json({});
    }

    json target_decision = decision.items().end().value();

    string detect_inside_timestamp = target_decision["timestamp"]["detect_inside"].get<string>();
    string vehicle_person_front_timestamp = target_decision["timestamp"]["vehicle_person_front"].get<string>();
    string vehicle_person_back_timestamp = target_decision["timestamp"]["vehicle_person_back"].get<string>();
    string road_timestamp = target_decision["timestamp"]["road"].get<string>();


    json target_detect_inside = detect_inside[detect_inside_timestamp];
    json target_vehicle_person_front = vehicle_person_front[vehicle_person_front_timestamp];
    json target_vehicle_person_back = vehicle_person_back[vehicle_person_back_timestamp];
    json target_road = road[road_timestamp];

    int vehicle_id = -1;
    int person_id = -1;
    int face_id = target_decision["car_inside"]["face_id"].get<int>();

    // Give up if target not exist
    if (target_detect_inside.empty() or target_vehicle_person_front.empty() or target_vehicle_person_back.empty()
    or target_road.empty()) {
        return json({});
    }

    // Get new highest level alert
    Alert new_alert;
    // Level 5
    if(target_decision["car_outside_front"]["person"]["crash"].get<bool>()) {
        person_id = target_decision["car_outside_front"]["person"]["display_id"].get<int>();
        new_alert = alert_spec["4"];
    }
    else if (target_decision["car_outside_front"]["vehicle"]["crash"].get<bool>()) {
        vehicle_id = target_decision["car_outside_front"]["vehicle"]["display_id"].get<int>();
        new_alert = alert_spec["3"];
    }
    else if(target_decision["car_outside_back"]["person"]["crash"].get<bool>()) {
        person_id = target_decision["car_outside_back"]["person"]["display_id"].get<int>();
        new_alert = alert_spec["12"];
    }
    else if (target_decision["car_outside_back"]["vehicle"]["crash"].get<bool>()) {
        vehicle_id = target_decision["car_outside_front_back"]["vehicle"]["display_id"].get<int>();
        new_alert = alert_spec["11"];
    }
    else if(target_decision["car_inside"]["yawn"].get<bool>() or
            target_decision["car_inside"]["sleep"].get<bool>() or
            target_decision["car_inside"]["didnt_concentrate"].get<bool>()) {
        new_alert = alert_spec["10"];
    }
    // Level 3
    else if(target_decision["car_outside_front"]["front_overtake"]["overtake"].get<bool>() or
            target_decision["car_outside_front"]["back_overtake"]["overtake"].get<bool>()) {
        new_alert = alert_spec["1"];
    }
    else if(target_decision["car_outside_front"]["vehicle_brake"]["brake"].get<bool>()) {
        new_alert = alert_spec["2"];
    }
    else if(target_decision["car_outside_front"]["road_pass"].get<bool>()) {
        new_alert = alert_spec["5"];
    }
    else if(target_decision["car_outside_front"]["vehicle_change_road"]["change_road"].get<bool>()) {
        new_alert = alert_spec["7"];
    }
    else if(target_decision["car_inside"]["use_phone"].get<bool>()) {
        new_alert = alert_spec["8"];
    }
    // Level 2
    else if(target_decision["car_outside_front"]["rough_road"].get<bool>()) {
        new_alert = alert_spec["6"];
    }
    else if(target_decision["car_outside_front"]["use_cigarette"].get<bool>()) {
        new_alert = alert_spec["9"];
    }
    // Level 0
    else {
        new_alert = alert_spec["0"];
    }

    if (new_alert.level >= cur_alert.level) {
        cur_alert = new_alert;
    }

    json result_without_timestamp;

    result_without_timestamp["alter_level"] = cur_alert.level;

    result_without_timestamp["class_location_front"] = json::array();

    // Vehicle detected
    for(const json& vehicle: target_vehicle_person_front["vehicle"]) {
        json obj;
        obj["safe"] = vehicle["display_id"] == vehicle_id;
        obj["type"] = "vehicle";
        obj["tl"] = vehicle["location"]["tl"];
        obj["w"] = vehicle["location"]["w"];
        obj["h"] = vehicle["location"]["h"];
        obj["distance"] = vehicle["distance"];
        obj["relative_speed"] = vehicle["speed"];
        obj["display_id"] = vehicle["display_id"];
        result_without_timestamp["class_location_front"].push_back(obj);
    }
    for(const json& vehicle: target_vehicle_person_back["vehicle"]) {
        json obj;
        obj["safe"] = vehicle["display_id"] == vehicle_id;
        obj["type"] = "vehicle";
        obj["tl"] = vehicle["location"]["tl"];
        obj["w"] = vehicle["location"]["w"];
        obj["h"] = vehicle["location"]["h"];
        obj["distance"] = vehicle["distance"];
        obj["relative_speed"] = vehicle["speed"];
        obj["display_id"] = vehicle["display_id"];
        result_without_timestamp["class_location_back"].push_back(obj);
    }

    // Person detected
    for(const json& person: target_vehicle_person_front["person"]) {
        json obj;
        obj["safe"] = person["display_id"] == person_id;
        obj["type"] = "person";
        obj["tl"] = person["location"]["tl"];
        obj["w"] = person["location"]["w"];
        obj["h"] = person["location"]["h"];
        obj["distance"] = person["distance"];
        obj["display_id"] = person["display_id"];
        result_without_timestamp["class_location_front"].push_back(obj);
    }
    for(const json& person: target_vehicle_person_back["person"]) {
        json obj;
        obj["safe"] = person["display_id"] == person_id;
        obj["type"] = "person";
        obj["tl"] = person["location"]["tl"];
        obj["w"] = person["location"]["w"];
        obj["h"] = person["location"]["h"];
        obj["distance"] = person["distance"];
        obj["display_id"] = person["display_id"];
        result_without_timestamp["class_location_back"].push_back(obj);
    }

    result_without_timestamp["lane_points"] = target_road["lane_points"];

    // Road detected
    result_without_timestamp["road_detect"] = json::array();
    for(const json& surface: target_road["road_detect"]) {
        json obj;
        obj["road_type"] = surface["class"];
        obj["tl"] = surface["location"]["tl"];
        obj["w"] = surface["location"]["w"];
        obj["h"] = surface["location"]["h"];
        obj["distance"] = surface["distance"];
        result_without_timestamp["road_detect"].push_back(obj);
    }

    // Inside detected
    result_without_timestamp["inside_detect"] = json::array();
    for(const json& inside: target_detect_inside["class_detect"]) {
        json obj;
        obj["type"] = inside["object_class"];
        obj["tl"] = inside["location"]["tl"];
        obj["w"] = inside["location"]["w"];
        obj["h"] = inside["location"]["h"];
        result_without_timestamp["inside_detect"].push_back(obj);
    }

    // Face detected
    if (face_id != -1) {
        result_without_timestamp["inside_face"] = target_detect_inside["face_detect"][face_id];
    }
    else {
        result_without_timestamp["inside_face"] = json({});
    }

    result_without_timestamp["road_points"] = target_road["road_points"];

    json result;

    result[to_string(time_ns())] = result_without_timestamp;

    result_buffer.push_back(result);

    if (result_buffer.size() > MAX_BUFFER) {
        result_buffer.erase(result_buffer.begin());
    }

    hardware_ctrl.set(cur_alert.color, cur_alert.buzzer_on, cur_alert.vibrator_on);

    return result;
}

DataProcessor::DataProcessor(char *alert_file) {
    cur_alert.level = 0;

    // Load alert_spec from file
    json alert_json;
    ifstream file_stream(alert_file);
    file_stream >> alert_json;

    cout << alert_json.dump(2) << endl;

    // Convert json to more clear Alert structure
    generate_alert(alert_json);
}

void DataProcessor::generate_alert(nlohmann::json alert_json) {
    for(auto& item: alert_json.items()) {
        Alert alert;
        alert.code = atoi(item.key().data());
        alert.level = item.value()["level"].get<int>();
        alert.color = HardwareAlert::string2color(item.value()["hardware"]["light"].get<string>());
        alert.buzzer_on = item.value()["hardware"]["buzzer"].get<bool>();
        alert.vibrator_on = item.value()["hardware"]["vibrator"].get<bool>();
        alert_spec[item.key()] = alert;
    }
}
